import processing.serial.*;
import cc.arduino.*;
//import fullscreen.*; 

//FullScreen fs; 
PApplet parent = this;
final int  RatingImage = 0, SlideShow = 1, Idle = 2, ShowResult = 3, PrepareRating = 4, ViewMotionDetection = 5;  
final int tryVoteTime = 10000;
float dt = 0;
float votingCountDown = tryVoteTime;
float displayTime = 0;
float countDownTimer = Float.MAX_VALUE;
float resultTimer = Float.MAX_VALUE;
float oldTime = 0;
boolean ratingImage = false;
ImageEntry currentImageEntry = null;
int state = Idle;
boolean useArduino = false;
Arduino arduino;
int maxScore = 9;
int pinOffset = 3;
int demoCounter = -1;
int demoDirection = 1;
int demoTime = 0;
int voteCountDown = -1;
int lastVoteCountDown = -1;
float peoplePresenceDetection = 0.0;

PFont fontConsole = createFont("Courier", 16, true);
PFont fontLarge = createFont("GillSans", 360, true);
PFont fontMedium = createFont("GillSans", 50, true);
String consoleString = "";
String errorMessage = "";
String entriesText = "";

final float VOTING_TIME = 5000;
final float CALIBRATION_TIME = 3000;
final float CALIBRATION_UPPER_LIMIT = 0.2;
final float SLIDE_TIME  = 6000;
final float COUNT_DOWN_TIME = 1000;
final float LOW_SCORE = 1.0;
final float HIGH_SCORE = 8.0;
final int MOVEMENT_THRESHOLD = 50;
// Play with these values in order to change score sensitivity
final float RAW_HIGH_LOUDNESS_SCORE = 0.03;
final float RAW_HIGH_MOTION_SCORE = 0.35;
final float PEOPLE_PRESENCE_THRESHOLD = 0.5;

ImageData imageData;
P5Properties props;

void setup() {

  try {
    props=new P5Properties();
    FileInputStream fis = new FileInputStream(sketchPath("") + "config.prop");
    props.load(fis);
    fis.close();
    int w = props.getIntProperty("viewport.width");
    int h = props.getIntProperty("viewport.height");
    useArduino = props.getBooleanProperty("useArduino", false);
    size(w,h);
    frameRate(10);
//    fs = new FullScreen(this); 
   // fs.enter(); 
  }
  catch(Exception e) {
    println("Could not read config.prop. Shutting down.");
    exit();
  }
  
  if (useArduino) {
    arduino = new Arduino(this, Arduino.list()[0]);
    for (int i = 3; i < 14; i++)  {
      arduino.pinMode(i, Arduino.OUTPUT);
    }
  }
  
  setupMotionDetection();
  setupSoundDetection();
  imageData = new ImageData();
  oldTime = millis();
}

void keyTyped()
{
  switch(key)
  {
  case 8: 
    consoleBackspace(); 
    break;
  case 10: 
    consoleEnter(); 
    break;    
  default: 
    consoleString += key; 
    break;
  }
}

void consoleBackspace()
{
  if(consoleString.length() > 0) 
    consoleString = consoleString.substring(0, consoleString.length()-1);
}

void consoleEnter()
{
  errorMessage = "";

  String[] parts = splitTokens(consoleString, " ");

  if(parts.length == 2 && parts[0].equals("show"))
  {
    state = ShowResult;
    try {
      Integer.parseInt(parts[1]);
      setImage(imageData.getEntry(Integer.parseInt(parts[1])).fileName);
    } 
    catch (Exception e) {
      setImage(parts[1]);
    }
  }
  else if(parts.length == 2 && parts[0].equals("file"))
  {
    try
    {
      File f = new File(props.getProperty("absoluteDataPath") + parts[1]);
      errorMessage = "File exists: " + f.exists() + "[" + f.getAbsolutePath() + "]";
    } 
    catch (Exception e) {
    }
  }
  else if(parts.length == 2 && parts[0].equals("download"))
  {
    imageData.requestFile(parts[1]);
  }
  else if(parts.length == 3 && parts[0].equals("download") && parts[1].equals("id"))
  {
    imageData.requestFile(Integer.parseInt(parts[2]));
  }
  else if(parts.length == 1 && parts[0].equals("downloaded"))
  {
    println(imageData.numberOfImages());
  }
  else if(parts.length == 1 && parts[0].equals("vote"))
  {
    state = PrepareRating;
  }
  else if(parts.length == 5 && parts[0].equals("rate"))
  {
    imageData.rate(Integer.parseInt(parts[1]), Integer.parseInt(parts[2]), 
    Integer.parseInt(parts[3]), Integer.parseInt(parts[4]));
  }
  else if(parts.length == 2 && parts[0].equals("rate"))
  {
    if (imageData.exists(Integer.parseInt(parts[1]))) {
      currentImageEntry = imageData.getEntry(Integer.parseInt(parts[1]));
      state = PrepareRating;
    }
    else {
      errorMessage = "No image with id=" + Integer.parseInt(parts[1]) + " found.";
    }
  }
  else if(parts.length == 1 && parts[0].equals("entries"))
  {
    println(imageData);
  }  
  else if(parts.length == 2 && parts[0].equals("config"))
  {
    println(props.getProperty(parts[1]));
  }
  else if(parts.length == 3 && parts[0].equals("config"))
  {
    String oldVal = props.getProperty(parts[1]);
    try {
      props.setProperty(parts[1], parts[2]);
      FileOutputStream fos = new FileOutputStream(sketchPath("") + "config.prop");
      props.store(fos, null);
      fos.close();
    } 
    catch (IOException e) { 
      println("Error saving prop-file.");
      e.printStackTrace();
    }  
    println(parts[1] + " updated. Was: " + oldVal + ", is: " + props.getProperty(parts[1]));
  }  
  else if(parts.length == 1 && parts[0].equals("slide"))
  {
    state = SlideShow;
  }  
  else if(parts.length == 1 && parts[0].equals("startmotion"))
  {
    state = ViewMotionDetection;
  }
  else if(parts.length == 1 && parts[0].equals("stopmotion"))
  {
    state = SlideShow;
  }
  else
  {
    errorMessage = "No such command!";
  }

  consoleString = "";
}

void draw() {
  // Causing flickering
  if(state != ViewMotionDetection) {
    background(0);
  }

  dt = millis() - oldTime;
  oldTime = millis();

  handleMotionDetection();
  handleSoundDetection();
  motionDetectionCalibration();
  soundDetectionCalibration();
  imageData.handleData();

  switch(state) {

  case RatingImage: 
    rateImage(); 
    break;

  case SlideShow: 
    slideShow(); 
    break;

  case Idle: 
    idle(); 
    break;

  case ShowResult: 
    showResult(); 
    break;

  case PrepareRating: 
    prepareRating(); 
    break;
    
   case ViewMotionDetection: 
    // do nothing
    break;

  }
  printCountDown();
  drawConsole();
}

void drawConsole()
{
  textFont(fontConsole);
  stroke(0,0,0);
  fill(0,0,0);
  if(consoleString.length() > 0)
    rect(0,props.getIntProperty("viewport.height")-18,props.getIntProperty("viewport.width"),18);
  if(errorMessage.length() > 0)
    rect(0,0,props.getIntProperty("viewport.width"),18);

  fill(255,255,255);
  text(consoleString, 10, props.getIntProperty("viewport.height")-4);
  fill(255,0,0);
  text(errorMessage, 10, 15);

  text(entriesText, 0, 19, props.getIntProperty("viewport.width"), props.getIntProperty("viewport.height")-19);
}

float textBlinkTime = 500;
  
void rateImage() {
  displayTime += dt;
  textBlinkTime -= dt;
  displayImage();
  
  if(textBlinkTime > 250)
    textCentered("Vote!");
  else if(textBlinkTime < 0)
    textBlinkTime = 500;
    
  if(displayTime > VOTING_TIME) {
    displayTime = 0;
    countDownTimer = Float.MAX_VALUE;
    ratingImage = false;
    float motionScore = getMotionDetectionRating();
    float soundScore = getSoundDetectionRating();
    float totalScore = 0.0;
    totalScore = ceil((motionScore + soundScore) / 2);
    println("  Motion Score: " + motionScore);
    println("Loudness Score: " + soundScore);
    println("   Total Score: " + totalScore);
    imageData.rate(currentImageEntry.id, ceil(soundScore), ceil(motionScore), ceil(totalScore));
    resultTimer = Float.MAX_VALUE;
    state = ShowResult;
    stopSound();
    playSound("bang_6.wav");
  }
}

void slideShow() {
  displayTime += dt;
  
  
  if(displayTime > SLIDE_TIME) {
    float f = random(1.0);
    if (f < 0.25) {
      currentImageEntry = imageData.getImageEntry("promo.jpg");
    } else {
    currentImageEntry = imageData.getImageEntry(floor(random(imageData.numberOfImages())));
    }
    if (!(currentImageEntry == null)) {
      if (currentImageEntry.rateTotal != 0) {
        meterShowScore(currentImageEntry.rateTotal);
      }
    }
    displayTime = 0;
  }

  if (!(currentImageEntry == null)) {  
    if (currentImageEntry.rateTotal == 0 || currentImageEntry.fileName.equals("promo.jpg")) {
      meterShowDemo();
    }
    try { 
      setImage(currentImageEntry.fileName);
      displayImage();
    } 
    catch (Exception e) {
      println("Error displaying image:");
      e.printStackTrace();
    }
  }
   peoplePresenceDetection = motionCalibrationValue;
//  println(peoplePresenceDetection);
   if(peoplePresenceDetection > PEOPLE_PRESENCE_THRESHOLD) {
     peoplePresenceDetection = 0;
//     state = PrepareRating;   
   }
}


void idle() {
  state = SlideShow;
}

void showResult() {
  resultTimer += dt;

  if(resultTimer == Float.MAX_VALUE) {   
    resultTimer = 0;
    meterShowScore(currentImageEntry.rateTotal);
  } 
  else if (resultTimer > SLIDE_TIME) {
    state = SlideShow;
  }

  displayImage();
  //textCentered(currentImageEntry.rateTotal + "");
}

void prepareRating() {
    countDownTimer += dt;
    
  
  
    if (countDownTimer > (COUNT_DOWN_TIME * 5.0)) {
      countDownTimer = 0;
      voteCountDown = -1;
    }
  
    if (countDownTimer > (COUNT_DOWN_TIME * 4.0)) {
      displayTime = 0;
      //println(imageData.getLeastRated(0).id);
      //println(imageData.getLeastRated(0).fileName);
      //println(imageData.getLeastRated(0).viewCount);
      currentImageEntry = imageData.getLeastRated(0);
      setImage(currentImageEntry.fileName);
      voteCountDown = -1;
      state = RatingImage;
      playSound("bb.mp3");
    } 
    else  if (countDownTimer > (COUNT_DOWN_TIME * 3.0)) {
      if (voteCountDown == 2) {
        playSound("bang_1.wav");
      }      
      voteCountDown = 3;
      textCentered("3");
    } 
    else if (countDownTimer > (COUNT_DOWN_TIME * 2.0)) {  
      if (voteCountDown == 1) {
        playSound("bang_1.wav");
      }
      voteCountDown = 2;
      textCentered("2");
    } 
    else if (countDownTimer > (COUNT_DOWN_TIME * 1.0)) {  
      if (voteCountDown == -1) {
        playSound("bang_1.wav");
      }
      meterClear();
      voteCountDown = 1;
      textCentered("1");
    }
}

void meterShowScore(int score) {
  if (useArduino) {
    for (int i = 0;  i < maxScore ; i++) {
        arduino.digitalWrite(i + pinOffset, ((i==score)?1:0));
    }
  demoCounter = score;
  }
}

void meterClear() {
  if (useArduino) {
    for (int i = 0;  i < maxScore ; i++) {
      arduino.digitalWrite(i + pinOffset, 0);
    }
    
  }
}

void meterShowDemo() {
  if (useArduino) {
    demoTime += dt;
  
    if(demoTime > 200) {
      demoTime = 0;
      if (demoCounter == -1) 
        demoCounter = 0;
      else if (demoCounter == maxScore - 1)
        demoDirection = -1;
      else if (demoCounter == 0)
        demoDirection = 1;
        
      demoCounter += demoDirection;
      //arduino.digitalWrite(demoCounter + pinOffset, 1);
      //arduino.digitalWrite(demoCounter - 1*demoDirection + pinOffset, 0);
      for (int i = 0;  i < maxScore ; i++) {
          arduino.digitalWrite(i + pinOffset, ((i<demoCounter)?1:0));
      }
    }
  }
}

void textCentered(String s) {
  textFont(fontLarge);
  textAlign(CENTER, CENTER);
  text(s, props.getIntProperty("viewport.width") / 2, props.getIntProperty("viewport.height") / 2);
  textAlign(LEFT, BASELINE);
}

void printCountDown() {
  if (state == SlideShow && imageData.hasUnrated()) {
    if (votingCountDown <= 0) {
    //  if (imageData.hasUnrated()) {
        state = PrepareRating;
    //  }
      votingCountDown = tryVoteTime;
    }
      
    textFont(fontMedium);    
    votingCountDown = votingCountDown - dt;
    if (ceil(votingCountDown / 1000) != 0) {
      textAlign(RIGHT, BASELINE);
      fill(255, 255, 255);
      text(ceil(votingCountDown / 1000), props.getIntProperty("viewport.width")*0.95, props.getIntProperty("viewport.height")*0.95);
      textAlign(LEFT, BASELINE);
    }
  }
}

void stop() {
  stopSound();
  stopSoundDetection();
  super.stop();
}
